import * as React from 'react';
import {ipcRenderer} from 'electron';

import store from '../../store';
import CanvasView from './CanvasView';
import CanvasSelectionBox, {CanvasSelectionBoxProps} from './CanvasSelectionBox';
import {IDesign} from '../../types/IDesign';
import {IView} from '../../types/IView';
import isParentView from '../../types/IParentView';
import {IPoint} from './IPoint';
import {IBoxRect} from './IBoxRect';
import BoxRect from './BoxRect';

import './CanvasPane.css';
import {ActionType} from '../../store/ActionType';

export interface CanvasPaneProps {
  design: IDesign;
  selectedView: IView;
}

export interface CanvasPaneState {
  selectionBoxRect: IBoxRect;
  cssTimestamp: number;
}

interface ViewRef {
  [viewId: string]: Element;
}

class CanvasPane extends React.Component<CanvasPaneProps, CanvasPaneState> {
  private canvasRef: Element;
  private viewRefs: ViewRef = {};
  private animationFrame: number;

  constructor(props: CanvasPaneProps) {
    super(props);

    this.state = {
      selectionBoxRect: new BoxRect(undefined),
      cssTimestamp: Date.now()
    };

    ipcRenderer.on('updated-class', (event: any, designId: string) => {
      if (designId !== this.props.design.id) {
        return;
      }
      this.setState({...this.state, cssTimestamp: Date.now()});
    });
  }

  componentDidMount() {
    if (!this.canvasRef) {
      return;
    }

    this.canvasRef.addEventListener('scroll', this.scrollHandler);
  }

  componentWillUnmount() {
    if (!this.canvasRef) {
      return;
    }

    this.canvasRef.removeEventListener('scroll', this.scrollHandler);
  }

  render() {
    const {design} = this.props;
    const cssHref = `str://${design.id}.css?t=${this.state.cssTimestamp}`;

    const updateCanvasRef = el => this.canvasRef = el;

    return (
      <div id={design.id} className="Canvas" onClick={this.trySelectView} ref={updateCanvasRef}>
        <link rel="stylesheet" type="text/css" media="screen" href={cssHref}/>
        {this.renderView(design.root)}
        {this.renderSelectionBox()}
      </div>
    );
  }

  componentDidUpdate() {
    // 要在views 渲染完了之后才去更新选择框的位置, 不然会获取到渲染前的状态
    this._updateSelectionBox();
  }

  private renderView(view: IView) {
    return (
      <CanvasView key={view.id} view={view} refUpdater={this.refUpdater}/>
    );
  }

  private refUpdater = (view: IView, ref: Element) => {
    this.viewRefs[view.id] = ref;
  }

  private renderSelectionBox() {
    if (this.props.selectedView) {
      const scrollTop = this.canvasRef ? this.canvasRef.scrollTop : 0;
      const props: CanvasSelectionBoxProps = {
        selectedView: this.props.selectedView,
        boxRect: this.state.selectionBoxRect,
        canvasOffsetTop: -scrollTop + 42,
        canvasOffsetLeft: 200
      };
      return (
        <CanvasSelectionBox {...props}/>
      );
    }
  }

  private _updateSelectionBox() {
    if (!this.props.selectedView) {
      return;
    }

    const boundRect = this.getBoundRect(this.props.selectedView);
    const boxRect = new BoxRect(boundRect);

    if (!boxRect.isEquals(this.state.selectionBoxRect)) {
      // 当大小和位置变化时才更新state, 不然会无限循环的.
      this.setState({...this.state, selectionBoxRect: boxRect});
    }
  }

  private trySelectView = (event: React.MouseEvent<any>) => {
    const mousePoint = {
      x: event.clientX,
      y: event.clientY
    };

    const views = [this.props.design.root];
    const selectedView = this._findView(views, mousePoint);
    if (!selectedView) {
      return;
    }

    this._doSelectView(selectedView);
  }

  private _findView(views: IView[], mousePoint: IPoint): IView | null {
    if (!views || views.length === 0) {
      return null;
    }

    const foundViews = views
      .map(view => {
        if (isParentView(view)) {
          // 如果包含有子组件, 则优先看子组件是否在鼠标点击的位置
          const foundChildView = this._findView(view.children, mousePoint);
          if (foundChildView != null) {
            // 如果找到有这样的子组件, 则返回这个子组件, 当前组件就不用再判断了
            return foundChildView;
          }
        }

        // 如果没有子组件, 或者子组件没有在鼠标点击的位置上, 则看当前组件是否在鼠标点击的位置
        if (this.isViewContains(view, mousePoint)) {
          // 如果是, 则返回当前组件
          return view;
        }

        // 如果不是, 则返回null
        return null;
      })
      // 因为上面的返回的组件会有为null 的情况, 这里就需要先进行一下过滤, 去掉那些为null 的.
      .filter(view => !!view);

    // 有可能有多个view 包含的鼠标点击的位置, 这时候要选择一个最合适的
    // TODO, 暂时只是先获取最后一个了
    if (foundViews.length > 0) {
      return foundViews[foundViews.length - 1];
    }

    // 所有这些组件(包括子组件) 都不再鼠标点击的位置上, 则返回null
    return null;
  }

  private _doSelectView(view: IView) {
    store.dispatch({
      type: ActionType.SELECT_VIEW,
      designId: this.props.design.id,
      view
    });
  }

  private isViewContains(view: IView, point: IPoint) {
    const rect = this.getBoundRect(view);
    return new BoxRect(rect).isContains(point);
  }

  private getBoundRect(view: IView) {
    const ref = this.viewRefs[view.id];
    if (!ref) {
      return undefined;
    }
    return ref.getBoundingClientRect();
  }

  private scrollHandler = (event: Event) => {
    if (this.animationFrame) {
      cancelAnimationFrame(this.animationFrame);
      this.animationFrame = null;
    }

    this.animationFrame = requestAnimationFrame(() => {
      this.animationFrame = null;
      store.dispatch({
        type: ActionType.UPDATE_SCROLL_TOP,
        designId: this.props.design.id,
        scrollTop: event.srcElement.scrollTop
      });
    });
  }
}

export default CanvasPane;
